using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class ChoiceAssociativityRewriterSpecification {
        [Test]
        public void IfNoInfixNonTerminalsNoRewritingNecessary () {
            var baseProduction = new Mock<IProduction> ().Object;

            var productions = new List<IProduction> ();
            var one = AddNewProduction (productions);
            var two = AddNewProduction (productions);
            var three = AddNewProduction (productions);
            var four = AddNewProduction (productions);

            var rewriter = new ChoiceAssociativityRewriter ();

            IEnumerable<IProduction> associativeProduction = rewriter.RewriteForAssciativity (productions, baseProduction);
            Assert.That (associativeProduction, Is.EqualTo (new[] {one, two, three, four}));
        }

        [Test]
        public void IfContainsInfixOperatorShouldCreateNewChoiceProductionsForOperatorAndHigherPrecedenceProductions () {
            var baseProduction = new Mock<IProduction> ().Object;
            var productions = new List<IProduction> ();
            var one = AddRightRecursiveOperator (productions, baseProduction);
            var two = AddNewProduction (productions);
            var three = AddNewProduction (productions);

            var rewriter = new ChoiceAssociativityRewriter ();

            IEnumerable<IProduction> rewrittenProductions = rewriter.RewriteForAssciativity (productions, baseProduction);

            ChoiceProduction choiceProductionTwo = AssertChoiceProduction (AssertRightRecursive (rewrittenProductions, one, baseProduction, 0));
            Assert.That (choiceProductionTwo.Productions, Is.EqualTo (new[] {two, three}));
        }

        [Test]
        public void IfContainsTwoInfixOperatorsShouldCreateNewChoiceProductionsForOperatorAndHigherPrecedenceProductions () {
            var baseProduction = new Mock<IProduction> ().Object;
            var productions = new List<IProduction> ();
            var one = AddRightRecursiveOperator (productions, baseProduction);
            var two = AddNewProduction (productions);
            var three = AddRightRecursiveOperator (productions, baseProduction);
            var four = AddNewProduction (productions);

            var rewriter = new ChoiceAssociativityRewriter ();

            IEnumerable<IProduction> rewrittenProductions = rewriter.RewriteForAssciativity (productions, baseProduction);

            ChoiceProduction choiceProductionTwo = AssertChoiceProduction (AssertRightRecursive (rewrittenProductions, one, baseProduction, 0));
            Assert.That (choiceProductionTwo.Productions.Count (), Is.EqualTo (3));
            Assert.That (choiceProductionTwo.Productions.ElementAt (0), Is.EqualTo (two));

            var actualFour = AssertRightRecursive (choiceProductionTwo.Productions, three, choiceProductionTwo, 1);
            Assert.That (actualFour, Is.EqualTo (four));
        }

        [Test]
        public void FirstInfixOperatorShouldHaveRightOperandAsBaseProduction () {
            var baseProduction = new Mock<IProduction> ().Object;
            var productions = new List<IProduction> ();
            var one = AddNewProduction(productions);
            var two = AddRightRecursiveOperator(productions, baseProduction);
            var three = AddNewProduction (productions);
            var four = AddRightRecursiveOperator (productions, baseProduction);
            var five = AddNewProduction (productions);

            var rewriter = new ChoiceAssociativityRewriter ();

            IEnumerable<IProduction> rewrittenProductions = rewriter.RewriteForAssciativity (productions, baseProduction);

            Assert.That(rewrittenProductions.ElementAt(0), Is.SameAs(one));
            ChoiceProduction choiceProductionTwo = AssertChoiceProduction (AssertRightRecursive (rewrittenProductions, two, baseProduction, 1));
            Assert.That (choiceProductionTwo.Productions.Count (), Is.EqualTo (3));
            Assert.That (choiceProductionTwo.Productions.ElementAt (0), Is.EqualTo (three));

            var actualFour = AssertRightRecursive (choiceProductionTwo.Productions, four, choiceProductionTwo, 1);
            Assert.That (actualFour, Is.EqualTo (five));
        }

        [Test]
        public void IfContainsInfixAndOnlyOneProductionRemainsThenDontCreateChoice () {
            var baseProduction = new Mock<IProduction> ().Object;
            var productions = new List<IProduction> ();
            var one = AddRightRecursiveOperator (productions, baseProduction);
            var two = AddNewProduction (productions);

            var rewriter = new ChoiceAssociativityRewriter ();

            IEnumerable<IProduction> rewrittenProductions = rewriter.RewriteForAssciativity (productions, baseProduction);

            IProduction productionTwo = AssertRightRecursive (rewrittenProductions, one, baseProduction, 0);
            Assert.That (productionTwo, Is.SameAs (two));
        }

        private static IProduction AssertRightRecursive (IEnumerable<IProduction> productions, IProduction expectedOperatorProduction, IProduction baseProduction, int index) {
            Assert.That (productions.Count (), Is.EqualTo (index + 2));
            var operatorProduction = productions.ElementAt (index);
            Assert.That (operatorProduction, Is.EqualTo (expectedOperatorProduction));

            var remainingProductions = productions.ElementAt (index + 1);

            Assert.That (operatorProduction.InfixInformation.First.Production,
                         Is.SameAs (remainingProductions));
            Assert.That (operatorProduction.InfixInformation.Last.Production,
                         Is.SameAs (baseProduction));

            return remainingProductions;
        }

        private static IProduction AddRightRecursiveOperator (ICollection<IProduction> productions, IProduction baseProduction) {
            var first = new LiteralRule (baseProduction);
            var last = new LiteralRule (baseProduction);
            var sequenceRule = new SequenceRule (first,
                                         new KeywordRule ("+"),
                                         last);
            sequenceRule.SetInfixInformation (new InfixInformation (first, last, baseProduction));

            var op = new NonTerminal (null,
                                      sequenceRule);
            productions.Add (op);
            return op;
        }

        private static IProduction AddNewProduction (ICollection<IProduction> productions) {
            var production = new Mock<IProduction> ();
            production.SetupGet (p => p.InfixInformation).Returns (new Mock<IInfixInformation> ().Object);
            productions.Add (production.Object);
            return production.Object;
        }

        private static ChoiceProduction AssertChoiceProduction (IProduction associativeProduction) {
            Assert.That (associativeProduction, Is.InstanceOf (typeof (ChoiceProduction)));
            return (ChoiceProduction) associativeProduction;
        }
    }
}